Networks#
This tutorial provides a deep dive into PyPSA-GB’s network modeling, covering buses, lines, transformers, and different network resolution options.
What You’ll Learn#
Understanding network topology (buses, lines, transformers)
Different network models: Reduced, Zonal, ETYS
Network clustering for computational efficiency
Coordinate systems and bus mapping
ETYS network upgrades
Network Models in PyPSA-GB#
Model |
Buses |
Lines |
Use Case |
|---|---|---|---|
Reduced |
32 |
~100 |
Fast testing, simplified analysis |
Zonal |
17 |
~40 |
Regional aggregation |
ETYS |
~2000 |
~3000 |
Full transmission detail |
Clustered |
50-200 |
Variable |
Balance of detail and speed |
1. Setup#
[1]:
import pypsa
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
import folium
from pyproj import Transformer
from _map_utils import prepare_map_network, explore_network_map
warnings.filterwarnings('ignore')
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams['figure.figsize'] = [12, 8]
plt.rcParams['figure.dpi'] = 100
print(f"PyPSA version: {pypsa.__version__}")
PyPSA version: 1.0.7
2. Network Data Structure#
2.1 Load a Sample Network#
[2]:
# Load a reduced network for exploration
n = pypsa.Network("../../../resources/network/Historical_2015_reduced_solved.nc")
print("Network Components:")
print(f" Buses: {len(n.buses)}")
print(f" Lines: {len(n.lines)}")
print(f" Transformers: {len(n.transformers)}")
print(f" Links: {len(n.links)}")
print(f" Generators: {len(n.generators)}")
print(f" Loads: {len(n.loads)}")
print(f" Storage Units: {len(n.storage_units)}")
INFO:pypsa.network.io:Imported network 'Historical_2015_reduced (Full)' has buses, carriers, generators, lines, links, loads, storage_units, sub_networks
Network Components:
Buses: 32
Lines: 99
Transformers: 0
Links: 3
Generators: 1958
Loads: 32
Storage Units: 3
2.2 Buses#
Buses represent substations/nodes in the network. Key attributes:
x, y: Coordinates (OSGB36 meters for ETYS, WGS84 degrees for Reduced)
v_nom: Nominal voltage (kV)
carrier: Type (AC, DC)
[3]:
# Bus data
print("Bus DataFrame columns:")
print(n.buses.columns.tolist())
print(f"\nSample buses:")
n.buses.head(10)
Bus DataFrame columns:
['v_nom', 'type', 'x', 'y', 'carrier', 'unit', 'location', 'v_mag_pu_set', 'v_mag_pu_min', 'v_mag_pu_max', 'control', 'generator', 'sub_network', 'country']
Sample buses:
[3]:
| v_nom | type | x | y | carrier | unit | location | v_mag_pu_set | v_mag_pu_min | v_mag_pu_max | control | generator | sub_network | country | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| name | ||||||||||||||
| Beauly | 275.0 | 248167.625895 | 845011.730526 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Peterhead | 275.0 | 411831.011338 | 843821.450890 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Errochty | 275.0 | 274353.767551 | 761097.048529 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Denny/Bonnybridge | 275.0 | 292803.094635 | 692060.857310 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Neilston | 400.0 | 248762.765458 | 659923.319765 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Strathaven | 400.0 | 282090.582169 | 652781.644926 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Torness | 400.0 | 368385.821758 | 670040.692176 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Eccles | 400.0 | 385644.869695 | 642664.271164 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Harker | 400.0 | 345770.517596 | 559939.870092 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB | ||||
| Stella West | 400.0 | 421353.244668 | 565891.265585 | AC | 1.0 | 0.0 | inf | PQ | 0 | GB |
[4]:
# Voltage levels
print("Buses by voltage level:")
if 'v_nom' in n.buses.columns:
print(n.buses.groupby('v_nom').size())
Buses by voltage level:
v_nom
275.0 4
400.0 28
dtype: int64
[5]:
# Coordinate system detection
x_range = n.buses['x'].max() - n.buses['x'].min()
y_range = n.buses['y'].max() - n.buses['y'].min()
if x_range > 1000:
coord_system = "OSGB36 (British National Grid - meters)"
else:
coord_system = "WGS84 (Latitude/Longitude - degrees)"
print(f"Coordinate System: {coord_system}")
print(f"X range: {n.buses['x'].min():.2f} to {n.buses['x'].max():.2f}")
print(f"Y range: {n.buses['y'].min():.2f} to {n.buses['y'].max():.2f}")
Coordinate System: OSGB36 (British National Grid - meters)
X range: 128806.84 to 815766.92
Y range: 112990.04 to 845011.73
2.3 Lines#
Lines represent AC transmission circuits. Key attributes:
bus0, bus1: Connected buses
s_nom: Thermal rating (MVA)
x, r: Reactance and resistance (p.u.)
[6]:
print("Line DataFrame columns:")
print(n.lines.columns.tolist())
print(f"\nSample lines:")
n.lines[['bus0', 'bus1', 's_nom', 'x', 'r']].head(10)
Line DataFrame columns:
['bus0', 'bus1', 'type', 'x', 'r', 'g', 'b', 's_nom', 's_nom_mod', 's_nom_extendable', 's_nom_min', 's_nom_max', 's_nom_set', 's_max_pu', 'capital_cost', 'active', 'build_year', 'lifetime', 'length', 'carrier', 'terrain_factor', 'num_parallel', 'v_ang_min', 'v_ang_max', 'sub_network', 'x_pu', 'r_pu', 'g_pu', 'b_pu', 'x_pu_eff', 'r_pu_eff', 's_nom_opt', 'v_nom']
Sample lines:
[6]:
| bus0 | bus1 | s_nom | x | r | |
|---|---|---|---|---|---|
| name | |||||
| 0 | Beauly | Peterhead | 525.0 | 0.0200 | 0.01220 |
| 1 | Beauly | Errochty | 132.0 | 0.1500 | 0.00700 |
| 2 | Beauly | Peterhead | 525.0 | 0.0200 | 0.01220 |
| 3 | Beauly | Errochty | 132.0 | 0.1500 | 0.00700 |
| 4 | Peterhead | Denny/Bonnybridge | 760.0 | 0.0650 | 0.00040 |
| 5 | Peterhead | Denny/Bonnybridge | 760.0 | 0.0650 | 0.00040 |
| 6 | Errochty | Denny/Bonnybridge | 648.0 | 0.0410 | 0.00300 |
| 7 | Errochty | Denny/Bonnybridge | 648.0 | 0.0410 | 0.00300 |
| 8 | Denny/Bonnybridge | Torness | 1090.0 | 0.0135 | 0.00211 |
| 9 | Denny/Bonnybridge | Strathaven | 1500.0 | 0.0230 | 0.00130 |
[7]:
# Line capacity statistics
print("Line Capacity Statistics:")
print(f" Total: {len(n.lines)} lines")
print(f" Total capacity: {n.lines.s_nom.sum()/1000:.1f} GVA")
print(f" Average rating: {n.lines.s_nom.mean():.0f} MVA")
print(f" Max rating: {n.lines.s_nom.max():.0f} MVA")
print(f" Min rating: {n.lines.s_nom.min():.0f} MVA")
Line Capacity Statistics:
Total: 99 lines
Total capacity: 230.8 GVA
Average rating: 2331 MVA
Max rating: 6960 MVA
Min rating: 132 MVA
2.4 Links (HVDC and Interconnectors)#
Links represent DC connections and controllable power flow elements.
[8]:
if len(n.links) > 0:
print("Links (HVDC/Interconnectors):")
print(n.links[['bus0', 'bus1', 'p_nom', 'carrier']].head(10))
else:
print("No links in this network")
Links (HVDC/Interconnectors):
bus0 \
name
IC_Britned Kemsley
IC_IFA Sellindge
IC_East West Interconnector Deeside
bus1 p_nom \
name
IC_Britned HVDC_External_Netherlands_Maasvlakte 1000.0
IC_IFA HVDC_External_France_Calais 1988.0
IC_East West Interconnector HVDC_External_Ireland_Rush_North_Beach 505.0
carrier
name
IC_Britned DC
IC_IFA DC
IC_East West Interconnector DC
3. Network Visualization#
[9]:
# Interactive network map with WGS84 coordinates for the basemap
m = explore_network_map(
n,
map_style="light",
tooltip=True,
bus_size=50,
bus_size_factor=2.0,
branch_width_factor=2.0,
)
m
lon range: -6.09249939157784 4.058099999999997
lat range: 50.912041859956986 57.484467069924335
[9]:
4. Comparing Network Models#
4.1 Load Different Network Resolutions#
[10]:
# Try to load different network types
networks = {}
# Reduced
try:
networks['Reduced (32)'] = pypsa.Network("../../../resources/network/Historical_2015_reduced_solved.nc")
print(f"✓ Loaded Reduced network: {len(networks['Reduced (32)'].buses)} buses")
except FileNotFoundError:
print("✗ Reduced network not found")
# ETYS (full or clustered)
try:
networks['Clustered (~100)'] = pypsa.Network("../../../resources/network/HT35_clustered_solved.nc")
print(f"✓ Loaded Clustered network: {len(networks['Clustered (~100)'].buses)} buses")
except FileNotFoundError:
print("✗ Clustered network not found")
try:
networks['Full ETYS'] = pypsa.Network("../../../resources/network/Historical_2023_etys_solved.nc")
print(f"✓ Loaded Full ETYS network: {len(networks['Full ETYS'].buses)} buses")
except FileNotFoundError:
print("✗ Full ETYS network not found")
INFO:pypsa.network.io:Imported network 'Historical_2015_reduced (Full)' has buses, carriers, generators, lines, links, loads, storage_units, sub_networks
✓ Loaded Reduced network: 32 buses
INFO:pypsa.network.io:Imported network 'HT35_clustered (Clustered)' has buses, carriers, generators, lines, links, loads, storage_units, stores, sub_networks
✓ Loaded Clustered network: 297 buses
INFO:pypsa.network.io:Imported network 'Historical_2023_etys (Full)' has buses, carriers, generators, lines, links, loads, storage_units, sub_networks, transformers
✓ Loaded Full ETYS network: 2044 buses
[11]:
# Compare network statistics
if len(networks) > 0:
comparison = []
for name, net in networks.items():
comparison.append({
'Network': name,
'Buses': len(net.buses),
'Lines': len(net.lines),
'Transformers': len(net.transformers),
'Links': len(net.links),
'Generators': len(net.generators),
'Total Line Cap (GVA)': net.lines.s_nom.sum()/1000
})
pd.DataFrame(comparison).set_index('Network')
[12]:
# Visual comparison of networks using interactive maps
if len(networks) >= 1:
for name, net in networks.items():
map_net = prepare_map_network(net)
print(f"{name} ({len(net.buses)} buses, {len(net.lines)} lines)")
display(
map_net.plot.explore(
map_style="light",
tooltip=True,
bus_size=50,
bus_size_factor=2.0,
branch_width_factor=2.0,
)
)
Reduced (32) (32 buses, 99 lines)
Clustered (~100) (297 buses, 499 lines)
Full ETYS (2044 buses, 1592 lines)